package com.gobang.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gobang.common.constant.BusinessErrorCode;
import com.gobang.common.exception.BusinessException;
import com.gobang.domain.dto.PageRequest;
import com.gobang.domain.vo.PageData;
import com.gobang.domain.vo.points.PointsInfoVO;
import com.gobang.domain.vo.points.PointsLogItemVO;
import com.gobang.entity.PointsAccount;
import com.gobang.entity.PointsLog;
import com.gobang.entity.User;
import com.gobang.mapper.PointsAccountMapper;
import com.gobang.mapper.PointsLogMapper;
import com.gobang.mapper.UserMapper;
import com.gobang.service.PointsService;
import com.gobang.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class PointsServiceImpl implements PointsService {

    private static final long POINTS_CACHE_TTL_SECONDS = 10 * 60;

    private final PointsAccountMapper pointsAccountMapper;
    private final PointsLogMapper pointsLogMapper;
    private final UserMapper userMapper;
    private final RedisUtil redisUtil;

    @Override
    public PointsInfoVO getCurrentPoints() {
        Long userId = getCurrentUserId();
        Integer currentPoints = getPointsWithCache(userId);
        PointsInfoVO vo = PointsInfoVO.builder()
                .currentPoints(currentPoints)
                .build();
        return vo;
    }

    @Override
    public PageData<PointsLogItemVO> getPointsLogsForCurrentUser(PageRequest pageRequest) {
        Long userId = getCurrentUserId();
        int pageNum = pageRequest.getPageNum();
        int pageSize = pageRequest.getPageSize();
        if (pageSize > 50) {
            pageSize = 50;
        }
        Page<PointsLog> page = new Page<>(pageNum, pageSize);
        QueryWrapper<PointsLog> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        wrapper.orderByDesc("created_at");
        Page<PointsLog> resultPage = pointsLogMapper.selectPage(page, wrapper);
        List<PointsLogItemVO> voList = resultPage.getRecords().stream()
                .map(this::convertToPointsLogItemVO)
                .collect(Collectors.toList());
        PageData<PointsLogItemVO> pageData = PageData.of(
                (int) resultPage.getCurrent(),
                (int) resultPage.getSize(),
                resultPage.getTotal(),
                voList
        );
        return pageData;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustPointsForUser(Long userId, Integer delta, String reason) {
        if (userId == null) {
            throw new BusinessException(BusinessErrorCode.PARAM_MISSING, "用户ID不能为空");
        }
        if (delta == null) {
            throw new BusinessException(BusinessErrorCode.POINTS_DELTA_INVALID, "积分变更值不能为空");
        }
        if (delta == 0) {
            throw new BusinessException(BusinessErrorCode.POINTS_DELTA_INVALID, "积分变更值不能为0");
        }
        if (reason == null || reason.trim().isEmpty()) {
            throw new BusinessException(BusinessErrorCode.PARAM_INVALID, "调整原因不能为空");
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new BusinessException(BusinessErrorCode.PARAM_ERROR, "用户不存在");
        }
        QueryWrapper<PointsAccount> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        wrapper.last("FOR UPDATE");
        PointsAccount account = pointsAccountMapper.selectOne(wrapper);
        if (account == null) {
            throw new BusinessException(BusinessErrorCode.POINTS_ACCOUNT_NOT_FOUND);
        }
        int currentPoints = account.getCurrentPoints() != null ? account.getCurrentPoints() : 0;
        int effectiveDelta = delta;
        int newPoints = currentPoints + delta;
        if (newPoints < 0) {
            newPoints = 0;
            effectiveDelta = -currentPoints;
        }
        account.setCurrentPoints(newPoints);
        int rows = pointsAccountMapper.updateById(account);
        if (rows <= 0) {
            throw new BusinessException(BusinessErrorCode.POINTS_UPDATE_FAILED);
        }
        PointsLog logEntity = new PointsLog();
        logEntity.setUserId(userId);
        logEntity.setMatchId(null);
        logEntity.setDelta(effectiveDelta);
        logEntity.setPointsBefore(currentPoints);
        logEntity.setPointsAfter(newPoints);
        logEntity.setReason("adjust");
        int logRows = pointsLogMapper.insert(logEntity);
        if (logRows <= 0) {
            throw new BusinessException(BusinessErrorCode.POINTS_LOG_CREATE_FAILED);
        }
        String cacheKey = buildPointsCacheKey(userId);
        redisUtil.delete(cacheKey);
        log.info("调整用户积分成功, userId={}, delta={}, effectiveDelta={}, beforePoints={}, afterPoints={}",
                userId, delta, effectiveDelta, currentPoints, newPoints);
    }

    @Override
    public Integer getCurrentPoints(Long userId) {
        if (userId == null) {
            throw new BusinessException(BusinessErrorCode.PARAM_MISSING, "用户ID不能为空");
        }
        return getPointsWithCache(userId);
    }

    private Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new BusinessException(BusinessErrorCode.UNAUTHORIZED, "未登录");
        }
        String username = authentication.getName();
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(BusinessErrorCode.UNAUTHORIZED, "用户不存在");
        }
        return user.getId();
    }

    private Integer getPointsWithCache(Long userId) {
        String cacheKey = buildPointsCacheKey(userId);
        String cached = redisUtil.get(cacheKey);
        if (cached != null) {
            try {
                return Integer.parseInt(cached);
            } catch (NumberFormatException ex) {
                redisUtil.delete(cacheKey);
            }
        }
        QueryWrapper<PointsAccount> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        PointsAccount account = pointsAccountMapper.selectOne(wrapper);
        if (account == null) {
            // 在线自动兜底：如果监听器曾标记初始化失败，则在这里尝试重新初始化积分账户
            try {
                String failKey = "points:init:failed:" + userId;
                if (redisUtil.hasKey(failKey)) {
                    PointsAccount newAccount = new PointsAccount();
                    newAccount.setUserId(userId);
                    newAccount.setCurrentPoints(0);
                    int rows = pointsAccountMapper.insert(newAccount);
                    if (rows > 0) {
                        redisUtil.delete(failKey);
                        redisUtil.set(cacheKey, "0", POINTS_CACHE_TTL_SECONDS);
                        log.info("在线自动修复积分账户成功, userId={}", userId);
                        return 0;
                    } else {
                        log.error("在线自动修复积分账户失败, 插入行数为0, userId={}", userId);
                    }
                }
            } catch (Exception ex) {
                log.error("在线自动修复积分账户出现异常, userId={}", userId, ex);
            }
            throw new BusinessException(BusinessErrorCode.POINTS_ACCOUNT_NOT_FOUND);
        }
        int currentPoints = account.getCurrentPoints() != null ? account.getCurrentPoints() : 0;
        redisUtil.set(cacheKey, String.valueOf(currentPoints), POINTS_CACHE_TTL_SECONDS);
        return currentPoints;
    }

    private String buildPointsCacheKey(Long userId) {
        return "points:cache:" + userId;
    }

    private PointsLogItemVO convertToPointsLogItemVO(PointsLog logEntity) {
        Long id = logEntity.getId();
        Long matchId = logEntity.getMatchId();
        Integer delta = logEntity.getDelta();
        Integer before = logEntity.getPointsBefore();
        Integer after = logEntity.getPointsAfter();
        String reason = logEntity.getReason();
        LocalDateTime createdAt = logEntity.getCreatedAt();
        return PointsLogItemVO.builder()
                .logId(id)
                .matchId(matchId)
                .delta(delta)
                .pointsBefore(before)
                .pointsAfter(after)
                .reason(reason)
                .createdAt(createdAt)
                .build();
    }
}
